"""
PPT转PDF转换核心模块
"""

from typing import Union, Optional, List
from pathlib import Path
from contextlib import contextmanager
import gc
import tempfile
import os
import time
import threading

from ..utils.exceptions import PPTConversionError, FileNotFoundError, InvalidFileFormatError
from ..utils.logger import Logger

# 检查PPT转换依赖
try:
    import win32com.client
    import pythoncom
    HAS_WIN32COM = True
except ImportError:
    HAS_WIN32COM = False


class PPTConverter:
    """PPT转PDF转换器"""

    def __init__(self, logger: Optional[Logger] = None):
        self.logger = logger or Logger("PPTConverter")
        self.is_available = HAS_WIN32COM
        self._conversion_lock = threading.Lock()  # 防止并发转换
        self._last_conversion_time = 0  # 记录上次转换时间
        self._min_conversion_interval = 2.0  # 最小转换间隔（秒）
        self._test_com_availability()

        if not self.is_available:
            self.logger.warning("PPT转换功能不可用")

    def _test_com_availability(self):
        """测试COM功能是否真正可用"""
        if not HAS_WIN32COM:
            return

        try:
            # 尝试创建PowerPoint应用程序对象
            import pythoncom
            pythoncom.CoInitialize()

            ppt_app = win32com.client.Dispatch("PowerPoint.Application")
            ppt_app.Quit()
            del ppt_app

            pythoncom.CoUninitialize()
            self.logger.debug("COM功能测试通过")

        except Exception as e:
            self.logger.warning(f"COM功能测试失败: {e}")
            self.is_available = False
    
    def _wait_for_conversion_interval(self):
        """等待转换间隔，避免COM对象冲突"""
        current_time = time.time()
        time_since_last = current_time - self._last_conversion_time

        if time_since_last < self._min_conversion_interval:
            wait_time = self._min_conversion_interval - time_since_last
            self.logger.debug(f"等待转换间隔: {wait_time:.1f}秒")
            time.sleep(wait_time)

        self._last_conversion_time = time.time()

    @contextmanager
    def _com_context(self):
        """
        COM对象上下文管理器，确保资源正确释放
        """
        if not self.is_available:
            raise PPTConversionError("PPT转换功能不可用，请安装pywin32")

        # 等待转换间隔
        self._wait_for_conversion_interval()

        pythoncom.CoInitialize()
        ppt_app = None

        try:
            ppt_app = win32com.client.Dispatch("PowerPoint.Application")

            # 参考原始实现：不设置Visible=False，避免权限问题
            # 原始代码中这行被注释掉了：# ppt_app.Visible = False
            self.logger.debug("PowerPoint应用程序已启动（保持默认可见状态）")

            # 尝试禁用警告对话框（可选）
            try:
                ppt_app.DisplayAlerts = False
                self.logger.debug("已禁用PowerPoint警告对话框")
            except Exception as e:
                self.logger.debug(f"保持默认警告设置: {e}")

            yield ppt_app

        except Exception as e:
            self.logger.error(f"COM对象创建失败: {e}")
            raise PPTConversionError(f"无法启动PowerPoint应用程序: {e}")
        finally:
            # 逆向释放资源
            self._cleanup_com_resources(ppt_app)

    def _cleanup_com_resources(self, ppt_app):
        """清理COM资源"""
        try:
            if ppt_app:
                # 关闭所有演示文稿
                try:
                    presentation_count = ppt_app.Presentations.Count
                    self.logger.debug(f"关闭 {presentation_count} 个演示文稿")

                    while ppt_app.Presentations.Count > 0:
                        ppt_app.Presentations(1).Close()
                        time.sleep(0.1)  # 给一点时间让关闭操作完成
                except Exception as e:
                    self.logger.warning(f"关闭演示文稿时出错: {e}")

                # 退出应用程序
                try:
                    ppt_app.Quit()
                    self.logger.debug("PowerPoint应用程序已退出")
                except Exception as e:
                    self.logger.warning(f"退出PowerPoint时出错: {e}")

                # 删除引用
                try:
                    del ppt_app
                except:
                    pass
        except Exception as e:
            self.logger.warning(f"清理COM资源时出错: {e}")

        # 强制释放COM资源
        try:
            pythoncom.CoUninitialize()
            gc.collect()
            time.sleep(0.5)  # 给系统一点时间完全释放资源
            self.logger.debug("COM资源已释放")
        except Exception as e:
            self.logger.warning(f"释放COM资源时出错: {e}")
    
    def convert_to_pdf(self, input_path: Union[str, Path], output_path: Union[str, Path],
                      max_retries: int = 2) -> bool:
        """
        转换PPT到PDF

        Args:
            input_path: 输入PPT文件路径
            output_path: 输出PDF文件路径
            max_retries: 最大重试次数

        Returns:
            bool: 转换是否成功

        Raises:
            FileNotFoundError: 输入文件不存在
            InvalidFileFormatError: 文件格式无效
            PPTConversionError: 转换失败
        """
        if not self.is_available:
            raise PPTConversionError("PPT转换功能不可用")

        # 使用锁防止并发转换
        with self._conversion_lock:
            return self._convert_with_retry(input_path, output_path, max_retries)

    def _convert_with_retry(self, input_path: Union[str, Path], output_path: Union[str, Path],
                           max_retries: int) -> bool:
        """带重试的转换实现"""
        input_path = Path(input_path)
        output_path = Path(output_path)

        # 验证输入文件
        if not input_path.exists():
            raise FileNotFoundError(f"PPT文件不存在: {input_path}")

        if input_path.suffix.lower() not in ['.ppt', '.pptx']:
            raise InvalidFileFormatError(f"不是有效的PPT文件: {input_path}")

        # 创建输出目录
        output_path.parent.mkdir(parents=True, exist_ok=True)

        last_error = None

        for attempt in range(max_retries + 1):
            try:
                if attempt > 0:
                    self.logger.info(f"第 {attempt + 1} 次尝试转换PPT")
                    time.sleep(1.0 * attempt)  # 递增延迟

                return self._perform_conversion(input_path, output_path)

            except Exception as e:
                last_error = e
                self.logger.warning(f"转换尝试 {attempt + 1} 失败: {e}")

                # 清理可能创建的不完整文件
                if output_path.exists():
                    try:
                        output_path.unlink()
                        self.logger.debug("已清理不完整的输出文件")
                    except:
                        pass

                if attempt < max_retries:
                    continue
                else:
                    break

        # 所有重试都失败了
        self.logger.error(f"PPT转换最终失败，已尝试 {max_retries + 1} 次")
        raise PPTConversionError(f"PPT转换失败: {last_error}")

    def _perform_conversion(self, input_path: Path, output_path: Path) -> bool:
        """执行实际的转换操作，完全参考原始实现"""
        # 参考原始实现：直接使用COM对象，不用上下文管理器
        ppt_app = None
        presentation = None

        try:
            # 参考原始实现的COM初始化
            import pythoncom
            pythoncom.CoInitialize()

            self.logger.info(f"开始转换PPT: {input_path} -> {output_path}")

            # 创建PowerPoint应用程序
            ppt_app = win32com.client.Dispatch("PowerPoint.Application")
            # 参考原始实现：不设置Visible=False

            # 打开演示文稿（参考原始实现的简单方式）
            presentation = ppt_app.Presentations.Open(str(input_path))

            # 保存为PDF（参考原始实现）
            presentation.SaveAs(str(output_path), 32)  # 32为PDF格式代码

            self.logger.info(f"PPT转换成功: {output_path}")
            return True

        except Exception as e:
            self.logger.error(f"PPT转换失败: {e}")
            raise PPTConversionError(f"PPT转换失败: {e}")

        finally:
            # 参考原始实现的资源释放顺序
            if presentation:
                try:
                    presentation.Close()
                    del presentation
                except:
                    pass

            if ppt_app:
                try:
                    ppt_app.Quit()
                    del ppt_app
                except:
                    pass

            # 参考原始实现的COM清理
            try:
                import pythoncom
                pythoncom.CoUninitialize()
                gc.collect()
            except:
                pass

    def _open_presentation(self, ppt_app, input_path: Path):
        """打开演示文稿，参考原始实现的简单方式"""
        try:
            # 参考原始实现：使用最简单的打开方式
            # 原始代码：presentation = ppt_app.Presentations.Open(str(input_path))
            presentation = ppt_app.Presentations.Open(str(input_path.resolve()))
            self.logger.debug("使用原始方式成功打开演示文稿")
            return presentation

        except Exception as e:
            self.logger.error(f"打开演示文稿失败: {e}")
            raise PPTConversionError(f"无法打开演示文稿: {e}")

    def _save_as_pdf(self, presentation, output_path: Path) -> bool:
        """保存为PDF，参考原始实现的简单方式"""
        try:
            # 参考原始实现：直接使用SaveAs方法
            # 原始代码：presentation.SaveAs(str(output_path), 32)
            presentation.SaveAs(str(output_path.resolve()), 32)  # 32 = ppSaveAsPDF
            self.logger.debug("使用原始SaveAs方法成功保存PDF")
            return output_path.exists() and output_path.stat().st_size > 0

        except Exception as e:
            self.logger.error(f"SaveAs方法失败: {e}")
            raise PPTConversionError(f"保存PDF失败: {e}")
    
    def batch_convert(self, input_files: list, output_dir: Union[str, Path],
                     progress_callback: Optional[callable] = None, max_retries: int = 2) -> dict:
        """
        批量转换PPT文件

        Args:
            input_files: 输入文件列表
            output_dir: 输出目录
            progress_callback: 进度回调函数 callback(current, total, filename)
            max_retries: 每个文件的最大重试次数

        Returns:
            dict: 转换结果 {'success': [...], 'failed': [...]}
        """
        if not self.is_available:
            raise PPTConversionError("PPT转换功能不可用")

        output_dir = Path(output_dir)
        output_dir.mkdir(parents=True, exist_ok=True)

        results = {'success': [], 'failed': []}

        for i, input_file in enumerate(input_files):
            input_path = Path(input_file)
            output_path = output_dir / f"{input_path.stem}.pdf"

            try:
                if progress_callback:
                    progress_callback(i, len(input_files), input_path.name)

                success = self.convert_to_pdf(input_path, output_path, max_retries)
                if success:
                    results['success'].append(str(output_path))
                else:
                    results['failed'].append(str(input_path))

            except Exception as e:
                self.logger.error(f"批量转换失败 - {input_path}: {e}")
                results['failed'].append(str(input_path))

            if progress_callback:
                progress_callback(i + 1, len(input_files), input_path.name)

        self.logger.info(f"批量转换完成 - 成功: {len(results['success'])}, 失败: {len(results['failed'])}")
        return results

    def convert_ppt_to_temp_pdf(self, ppt_path: Union[str, Path]) -> Path:
        """将PPT完整转换为临时PDF文件，用于预览"""
        if not self.is_available:
            raise PPTConversionError("PPT转换功能不可用")

        ppt_path = Path(ppt_path)
        if not ppt_path.exists():
            raise FileNotFoundError(f"PPT文件不存在: {ppt_path}")

        if ppt_path.suffix.lower() not in ['.ppt', '.pptx']:
            raise InvalidFileFormatError(f"不是有效的PPT文件: {ppt_path}")

        # 创建临时PDF文件路径
        import tempfile
        temp_dir = Path(tempfile.gettempdir()) / "ppt_preview"
        temp_dir.mkdir(exist_ok=True)

        temp_pdf_path = temp_dir / f"{ppt_path.stem}_preview.pdf"

        # 使用锁防止并发转换
        with self._conversion_lock:
            return self._convert_ppt_to_pdf_direct(ppt_path, temp_pdf_path)

    def _convert_ppt_to_pdf_direct(self, ppt_path: Path, output_path: Path) -> Path:
        """直接转换PPT为PDF（参考原始实现）"""
        ppt_app = None
        presentation = None

        try:
            import pythoncom
            pythoncom.CoInitialize()

            self.logger.info(f"开始完整转换PPT为临时PDF: {ppt_path}")

            # 创建PowerPoint应用程序
            ppt_app = win32com.client.Dispatch("PowerPoint.Application")

            # 打开演示文稿（参考原始实现的简单方式）
            presentation = ppt_app.Presentations.Open(str(ppt_path))

            # 保存为PDF（参考原始实现）
            presentation.SaveAs(str(output_path), 32)  # 32为PDF格式代码

            self.logger.info(f"PPT完整转换成功: {output_path}")
            return output_path

        except Exception as e:
            self.logger.error(f"PPT完整转换失败: {e}")
            raise PPTConversionError(f"PPT完整转换失败: {e}")

        finally:
            # 参考原始实现的资源释放顺序
            if presentation:
                try:
                    presentation.Close()
                    del presentation
                except:
                    pass

            if ppt_app:
                try:
                    ppt_app.Quit()
                    del ppt_app
                except:
                    pass

            # 参考原始实现的COM清理
            try:
                import pythoncom
                pythoncom.CoUninitialize()
                gc.collect()
            except:
                pass

    def cleanup_temp_pdf(self, temp_pdf_path: Union[str, Path]):
        """清理临时PDF文件"""
        try:
            temp_pdf_path = Path(temp_pdf_path)
            if temp_pdf_path.exists():
                temp_pdf_path.unlink()
                self.logger.debug(f"已清理临时PDF文件: {temp_pdf_path}")

                # 尝试清理临时目录（如果为空）
                try:
                    temp_pdf_path.parent.rmdir()
                except:
                    pass  # 目录不为空或其他原因，忽略

        except Exception as e:
            self.logger.warning(f"清理临时PDF文件失败: {e}")


    
    def is_ppt_file(self, file_path: Union[str, Path]) -> bool:
        """
        检查是否为PPT文件
        
        Args:
            file_path: 文件路径
            
        Returns:
            bool: 是否为PPT文件
        """
        file_path = Path(file_path)
        return file_path.suffix.lower() in ['.ppt', '.pptx']
    
    def get_supported_formats(self) -> list:
        """
        获取支持的文件格式
        
        Returns:
            list: 支持的格式列表
        """
        return ['.ppt', '.pptx'] if self.is_available else []
